package com.xjd.edu.logging;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.LoggerContextVO;
import org.slf4j.Marker;

import java.util.Map;
import java.util.regex.Pattern;

/**
 * 实现对日志内容部分精准控制
 *
 * @author wangyu
 * @created 2025/3/15 13:49
 * @see ch.qos.logback.classic.spi.LoggingEvent
 */
public class EduLoggingEventWrapper implements ILoggingEvent {

    private final ILoggingEvent origin;

    // 身份证正则表达式
    final Pattern REGEX_ID_NO_18 = Pattern.compile("(\\d{6})"   // 6位地区码
            + "(19|20)\\d{2}"                                   // 年YYYY
            + "((0[1-9])|10|11|12)"                             // 月MM
            + "(([0-2][1-9])|10|20|30|31)"                      // 日DD
            + "(\\d{3}"                                         // 顺序码
            + "[\\dXx])");                                      // 校验码
    // 银行卡号正则表达式
    final Pattern REGEX_BANK_NO = Pattern.compile("(\\d{4})(\\d{8,12})(\\d{4})");
    // 手机号正则表达式
    final Pattern REGEX_MOBILE = Pattern.compile("(1\\d{2})(\\d{4})(\\d{4})");

    final String X_REQUEST_URI = "X-Request-Uri";

    // 16KB
    final int LENGTH = 1024 * 16;

    public EduLoggingEventWrapper(ILoggingEvent origin) {
        this.origin = origin;
    }

    @Override
    public String getFormattedMessage() {
        String txt = origin.getFormattedMessage();
        if (txt != null) {
            // 长度截断
            txt = cut(txt);
            // 脱敏（打码）
            txt = desensitized(txt);
        } else {
            txt = "";
        }
        // ERROR日志打印请求URI
        if (origin.getLevel().isGreaterOrEqual(Level.ERROR)) {
            return String.format("%s # %s", origin.getMDCPropertyMap().getOrDefault(X_REQUEST_URI, ""), txt);
        }
        return "# " + txt;
    }

    /**
     * 日志截断，避免过长日志
     *
     * @param txt
     * @return
     */
    private String cut(String txt) {
        if (txt.length() > LENGTH) {
            return txt.substring(0, LENGTH) + "\n";
        }
        return txt;
    }

    /**
     * 日志脱敏（打码）
     *
     * @param txt
     * @return
     */
    private String desensitized(String txt) {
        if (!txt.contains("%{") || !txt.contains("}%")) {
            return this.desensitized0(txt);
        }
        // 特殊处理，预留一个不打码的机制，应谨慎使用（一条日志消息只支持一组）
        final String[] parts = txt.split("(\\%\\{|\\}\\%)", 3);
        return new StringBuffer()
                .append(desensitized0(parts[0]))
                .append(parts[1])
                .append(desensitized0(parts[2]))
                .toString();
    }

    private String desensitized0(String txt) {
        // 身份证号
        txt = REGEX_ID_NO_18.matcher(txt).replaceAll("$1********$7");
        // 银行卡号
        txt = REGEX_BANK_NO.matcher(txt).replaceAll("$1 **** **** $3");
        // 手机号
        txt = REGEX_MOBILE.matcher(txt).replaceAll("$1****$3");
        return txt;
    }

    @Override
    public IThrowableProxy getThrowableProxy() {
        return origin.getThrowableProxy();
    }

    @Override
    public Level getLevel() {
        return origin.getLevel();
    }

    @Override
    public LoggerContextVO getLoggerContextVO() {
        return origin.getLoggerContextVO();
    }

    @Override
    public long getTimeStamp() {
        return origin.getTimeStamp();
    }

    @Override
    public void prepareForDeferredProcessing() {
        origin.prepareForDeferredProcessing();
    }

    /**
     * MDC 中字段也需要打码
     *
     * @return
     */
    @Override
    public Map<String, String> getMDCPropertyMap() {
        final Map<String, String> map = origin.getMDCPropertyMap();
        // 脱敏（打码），但此方法效率较差，需改进
        for (Map.Entry<String, String> entry : map.entrySet()) {
            map.put(entry.getKey(), desensitized(entry.getValue()));
        }
        return map;
    }

    @Override
    public Object[] getArgumentArray() {
        return origin.getArgumentArray();
    }

    @Override
    public Marker getMarker() {
        return origin.getMarker();
    }

    @Override
    public StackTraceElement[] getCallerData() {
        return origin.getCallerData();
    }

    @Override
    public boolean hasCallerData() {
        return origin.hasCallerData();
    }

    @Override
    public String getLoggerName() {
        return origin.getLoggerName();
    }

    @Override
    public String getMessage() {
        return origin.getMessage();
    }

    @Override
    public String getThreadName() {
        return origin.getThreadName();
    }

    @Override
    public Map<String, String> getMdc() {
        return this.getMDCPropertyMap();
    }

}
